/*
 * RoomController.java 20 nov. 2008
 *
 * Sweet Home 3D, Copyright (c) 2008 Emmanuel PUYBARET / eTeks <info@eteks.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package com.eteks.sweethome3d.viewcontroller;

import java.awt.BasicStroke;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.GeneralPath;
import java.awt.geom.Line2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoableEdit;
import javax.swing.undo.UndoableEditSupport;

import com.eteks.sweethome3d.model.Baseboard;
import com.eteks.sweethome3d.model.Home;
import com.eteks.sweethome3d.model.HomeTexture;
import com.eteks.sweethome3d.model.Level;
import com.eteks.sweethome3d.model.Room;
import com.eteks.sweethome3d.model.Selectable;
import com.eteks.sweethome3d.model.UserPreferences;
import com.eteks.sweethome3d.model.Wall;

/**
 * A MVC controller for room view.
 * @author Emmanuel Puybaret
 */
public class RoomController implements Controller
{
	/**
	 * The properties that may be edited by the view associated to this controller. 
	 */
	public enum Property
	{
		NAME, AREA_VISIBLE, FLOOR_VISIBLE, FLOOR_COLOR, FLOOR_PAINT, FLOOR_SHININESS, CEILING_VISIBLE, CEILING_COLOR, CEILING_PAINT, CEILING_SHININESS, SPLIT_SURROUNDING_WALLS, WALL_SIDES_COLOR, WALL_SIDES_PAINT, WALL_SIDES_SHININESS, WALL_SIDES_BASEBOARD
	}
	
	/**
	 * The possible values for {@linkplain #getFloorPaint() room paint type}.
	 */
	public enum RoomPaint
	{
		DEFAULT, COLORED, TEXTURED
	}
	
	private final Home home;
	private final UserPreferences preferences;
	private final ViewFactory viewFactory;
	private final ContentManager contentManager;
	private final UndoableEditSupport undoSupport;
	private TextureChoiceController floorTextureController;
	private TextureChoiceController ceilingTextureController;
	private TextureChoiceController wallSidesTextureController;
	private BaseboardChoiceController wallSidesBaseboardController;
	private final PropertyChangeSupport propertyChangeSupport;
	private DialogView roomView;
	
	private String name;
	private Boolean areaVisible;
	private Boolean floorVisible;
	private Integer floorColor;
	private RoomPaint floorPaint;
	private Float floorShininess;
	private Boolean ceilingVisible;
	private Integer ceilingColor;
	private RoomPaint ceilingPaint;
	private Float ceilingShininess;
	private boolean wallSidesEditable;
	private boolean splitSurroundingWalls;
	private boolean splitSurroundingWallsNeeded;
	private Integer wallSidesColor;
	private RoomPaint wallSidesPaint;
	private Float wallSidesShininess;
	
	/**
	 * Creates the controller of room view with undo support.  
	 */
	public RoomController(final Home home, UserPreferences preferences, ViewFactory viewFactory,
			ContentManager contentManager, UndoableEditSupport undoSupport)
	{
		this.home = home;
		this.preferences = preferences;
		this.viewFactory = viewFactory;
		this.contentManager = contentManager;
		this.undoSupport = undoSupport;
		this.propertyChangeSupport = new PropertyChangeSupport(this);
		
		updateProperties();
	}
	
	/**
	 * Returns the texture controller of the room floor.
	 */
	public TextureChoiceController getFloorTextureController()
	{
		// Create sub controller lazily only once it's needed
		if (this.floorTextureController == null)
		{
			this.floorTextureController = new TextureChoiceController(
					this.preferences.getLocalizedString(RoomController.class, "floorTextureTitle"), this.preferences,
					this.viewFactory, this.contentManager);
			this.floorTextureController.addPropertyChangeListener(TextureChoiceController.Property.TEXTURE,
					new PropertyChangeListener()
					{
						public void propertyChange(PropertyChangeEvent ev)
						{
							setFloorPaint(RoomPaint.TEXTURED);
						}
					});
		}
		return this.floorTextureController;
	}
	
	/**
	 * Returns the texture controller of the room ceiling.
	 */
	public TextureChoiceController getCeilingTextureController()
	{
		// Create sub controller lazily only once it's needed
		if (this.ceilingTextureController == null)
		{
			this.ceilingTextureController = new TextureChoiceController(
					this.preferences.getLocalizedString(RoomController.class, "ceilingTextureTitle"), this.preferences,
					this.viewFactory, this.contentManager);
			this.ceilingTextureController.addPropertyChangeListener(TextureChoiceController.Property.TEXTURE,
					new PropertyChangeListener()
					{
						public void propertyChange(PropertyChangeEvent ev)
						{
							setCeilingPaint(RoomPaint.TEXTURED);
						}
					});
		}
		return this.ceilingTextureController;
	}
	
	/**
	 * Returns the texture controller of the room wall sides.
	 */
	public TextureChoiceController getWallSidesTextureController()
	{
		// Create sub controller lazily only once it's needed
		if (this.wallSidesTextureController == null)
		{
			this.wallSidesTextureController = new TextureChoiceController(
					this.preferences.getLocalizedString(RoomController.class, "wallSidesTextureTitle"),
					this.preferences, this.viewFactory, this.contentManager);
			this.wallSidesTextureController.addPropertyChangeListener(TextureChoiceController.Property.TEXTURE,
					new PropertyChangeListener()
					{
						public void propertyChange(PropertyChangeEvent ev)
						{
							setWallSidesPaint(RoomPaint.TEXTURED);
						}
					});
		}
		return this.wallSidesTextureController;
	}
	
	/**
	 * Returns the controller of the wall sides baseboard.
	 * @since 5.0
	 */
	public BaseboardChoiceController getWallSidesBaseboardController()
	{
		// Create sub controller lazily only once it's needed
		if (this.wallSidesBaseboardController == null)
		{
			this.wallSidesBaseboardController = new BaseboardChoiceController(this.preferences, this.viewFactory,
					this.contentManager);
		}
		return this.wallSidesBaseboardController;
	}
	
	/**
	 * Returns the view associated with this controller.
	 */
	public DialogView getView()
	{
		// Create view lazily only once it's needed
		if (this.roomView == null)
		{
			this.roomView = this.viewFactory.createRoomView(this.preferences, this);
		}
		return this.roomView;
	}
	
	/**
	 * Displays the view controlled by this controller.
	 */
	public void displayView(View parentView)
	{
		getView().displayView(parentView);
	}
	
	/**
	 * Adds the property change <code>listener</code> in parameter to this controller.
	 */
	public void addPropertyChangeListener(Property property, PropertyChangeListener listener)
	{
		this.propertyChangeSupport.addPropertyChangeListener(property.name(), listener);
	}
	
	/**
	 * Removes the property change <code>listener</code> in parameter from this controller.
	 */
	public void removePropertyChangeListener(Property property, PropertyChangeListener listener)
	{
		this.propertyChangeSupport.removePropertyChangeListener(property.name(), listener);
	}
	
	/**
	 * Returns <code>true</code> if the given <code>property</code> is editable.
	 * Depending on whether a property is editable or not, the view associated to this controller
	 * may render it differently.
	 * The implementation of this method always returns <code>true</code> except for <code>WALL</code> properties. 
	 */
	public boolean isPropertyEditable(Property property)
	{
		switch (property)
		{
			case SPLIT_SURROUNDING_WALLS:
			case WALL_SIDES_COLOR:
			case WALL_SIDES_PAINT:
			case WALL_SIDES_SHININESS:
			case WALL_SIDES_BASEBOARD:
				return this.wallSidesEditable;
			default:
				return true;
		}
	}
	
	/**
	 * Updates edited properties from selected rooms in the home edited by this controller.
	 */
	protected void updateProperties()
	{
		List<Room> selectedRooms = Home.getRoomsSubList(this.home.getSelectedItems());
		if (selectedRooms.isEmpty())
		{
			setAreaVisible(null); // Nothing to edit
			setFloorColor(null);
			getFloorTextureController().setTexture(null);
			setFloorPaint(null);
			setFloorShininess(null);
			setCeilingColor(null);
			getCeilingTextureController().setTexture(null);
			setCeilingPaint(null);
			setCeilingShininess(null);
		}
		else
		{
			// Search the common properties among selected rooms
			Room firstRoom = selectedRooms.get(0);
			
			String name = firstRoom.getName();
			if (name != null)
			{
				for (int i = 1; i < selectedRooms.size(); i++)
				{
					if (!name.equals(selectedRooms.get(i).getName()))
					{
						name = null;
						break;
					}
				}
			}
			setName(name);
			
			// Search the common areaVisible value among rooms
			Boolean areaVisible = firstRoom.isAreaVisible();
			for (int i = 1; i < selectedRooms.size(); i++)
			{
				if (areaVisible != selectedRooms.get(i).isAreaVisible())
				{
					areaVisible = null;
					break;
				}
			}
			setAreaVisible(areaVisible);
			
			// Search the common floorVisible value among rooms
			Boolean floorVisible = firstRoom.isFloorVisible();
			for (int i = 1; i < selectedRooms.size(); i++)
			{
				if (floorVisible != selectedRooms.get(i).isFloorVisible())
				{
					floorVisible = null;
					break;
				}
			}
			setFloorVisible(floorVisible);
			
			// Search the common floor color among rooms
			Integer floorColor = firstRoom.getFloorColor();
			if (floorColor != null)
			{
				for (int i = 1; i < selectedRooms.size(); i++)
				{
					if (!floorColor.equals(selectedRooms.get(i).getFloorColor()))
					{
						floorColor = null;
						break;
					}
				}
			}
			setFloorColor(floorColor);
			
			// Search the common floor texture among rooms
			HomeTexture floorTexture = firstRoom.getFloorTexture();
			if (floorTexture != null)
			{
				for (int i = 1; i < selectedRooms.size(); i++)
				{
					if (!floorTexture.equals(selectedRooms.get(i).getFloorTexture()))
					{
						floorTexture = null;
						break;
					}
				}
			}
			getFloorTextureController().setTexture(floorTexture);
			
			boolean defaultColorsAndTextures = true;
			for (int i = 0; i < selectedRooms.size(); i++)
			{
				Room room = selectedRooms.get(i);
				if (room.getFloorColor() != null || room.getFloorTexture() != null)
				{
					defaultColorsAndTextures = false;
					break;
				}
			}
			
			if (floorColor != null)
			{
				setFloorPaint(RoomPaint.COLORED);
			}
			else if (floorTexture != null)
			{
				setFloorPaint(RoomPaint.TEXTURED);
			}
			else if (defaultColorsAndTextures)
			{
				setFloorPaint(RoomPaint.DEFAULT);
			}
			else
			{
				setFloorPaint(null);
			}
			
			// Search the common floor shininess among rooms
			Float floorShininess = firstRoom.getFloorShininess();
			for (int i = 1; i < selectedRooms.size(); i++)
			{
				if (!floorShininess.equals(selectedRooms.get(i).getFloorShininess()))
				{
					floorShininess = null;
					break;
				}
			}
			setFloorShininess(floorShininess);
			
			// Search the common ceilingVisible value among rooms
			Boolean ceilingVisible = firstRoom.isCeilingVisible();
			for (int i = 1; i < selectedRooms.size(); i++)
			{
				if (ceilingVisible != selectedRooms.get(i).isCeilingVisible())
				{
					ceilingVisible = null;
					break;
				}
			}
			setCeilingVisible(ceilingVisible);
			
			// Search the common ceiling color among rooms
			Integer ceilingColor = firstRoom.getCeilingColor();
			if (ceilingColor != null)
			{
				for (int i = 1; i < selectedRooms.size(); i++)
				{
					if (!ceilingColor.equals(selectedRooms.get(i).getCeilingColor()))
					{
						ceilingColor = null;
						break;
					}
				}
			}
			setCeilingColor(ceilingColor);
			
			// Search the common ceiling texture among rooms
			HomeTexture ceilingTexture = firstRoom.getCeilingTexture();
			if (ceilingTexture != null)
			{
				for (int i = 1; i < selectedRooms.size(); i++)
				{
					if (!ceilingTexture.equals(selectedRooms.get(i).getCeilingTexture()))
					{
						ceilingTexture = null;
						break;
					}
				}
			}
			getCeilingTextureController().setTexture(ceilingTexture);
			
			defaultColorsAndTextures = true;
			for (int i = 0; i < selectedRooms.size(); i++)
			{
				Room room = selectedRooms.get(i);
				if (room.getCeilingColor() != null || room.getCeilingTexture() != null)
				{
					defaultColorsAndTextures = false;
					break;
				}
			}
			
			if (ceilingColor != null)
			{
				setCeilingPaint(RoomPaint.COLORED);
			}
			else if (ceilingTexture != null)
			{
				setCeilingPaint(RoomPaint.TEXTURED);
			}
			else if (defaultColorsAndTextures)
			{
				setCeilingPaint(RoomPaint.DEFAULT);
			}
			else
			{
				setCeilingPaint(null);
			}
			
			// Search the common ceiling shininess among rooms
			Float ceilingShininess = firstRoom.getCeilingShininess();
			for (int i = 1; i < selectedRooms.size(); i++)
			{
				if (!ceilingShininess.equals(selectedRooms.get(i).getCeilingShininess()))
				{
					ceilingShininess = null;
					break;
				}
			}
			setCeilingShininess(ceilingShininess);
		}
		
		List<WallSide> wallSides = getRoomsWallSides(selectedRooms, null);
		if (wallSides.isEmpty())
		{
			this.wallSidesEditable = this.splitSurroundingWallsNeeded = this.splitSurroundingWalls = false;
			setWallSidesColor(null);
			setWallSidesPaint(null);
			setWallSidesShininess(null);
			getWallSidesBaseboardController().setVisible(null);
			getWallSidesBaseboardController().setThickness(null);
			getWallSidesBaseboardController().setHeight(null);
			getWallSidesBaseboardController().setColor(null);
			getWallSidesBaseboardController().getTextureController().setTexture(null);
			getWallSidesBaseboardController().setPaint(null);
		}
		else
		{
			this.wallSidesEditable = true;
			this.splitSurroundingWallsNeeded = splitWalls(wallSides, null, null, null);
			this.splitSurroundingWalls = false;
			WallSide firstWallSide = wallSides.get(0);
			
			// Search the common wall color among wall sides        
			Integer wallSidesColor = firstWallSide.getSide() == WallSide.LEFT_SIDE
					? firstWallSide.getWall().getLeftSideColor() : firstWallSide.getWall().getRightSideColor();
			if (wallSidesColor != null)
			{
				for (int i = 1; i < wallSides.size(); i++)
				{
					WallSide wallSide = wallSides.get(i);
					if (!wallSidesColor.equals(wallSide.getSide() == WallSide.LEFT_SIDE
							? wallSide.getWall().getLeftSideColor() : wallSide.getWall().getRightSideColor()))
					{
						wallSidesColor = null;
						break;
					}
				}
			}
			setWallSidesColor(wallSidesColor);
			
			// Search the common wall texture among wall sides
			HomeTexture wallSidesTexture = firstWallSide.getSide() == WallSide.LEFT_SIDE
					? firstWallSide.getWall().getLeftSideTexture() : firstWallSide.getWall().getRightSideTexture();
			if (wallSidesTexture != null)
			{
				for (int i = 1; i < wallSides.size(); i++)
				{
					WallSide wallSide = wallSides.get(i);
					if (!wallSidesTexture.equals(wallSide.getSide() == WallSide.LEFT_SIDE
							? wallSide.getWall().getLeftSideTexture() : wallSide.getWall().getRightSideTexture()))
					{
						wallSidesTexture = null;
						break;
					}
				}
			}
			getWallSidesTextureController().setTexture(wallSidesTexture);
			
			boolean defaultColorsAndTextures = true;
			for (int i = 1; i < wallSides.size(); i++)
			{
				WallSide wallSide = wallSides.get(i);
				if ((wallSide.getSide() == WallSide.LEFT_SIDE ? wallSide.getWall().getLeftSideColor()
						: wallSide.getWall().getRightSideColor()) != null
						|| (wallSide.getSide() == WallSide.LEFT_SIDE ? wallSide.getWall().getLeftSideTexture()
								: wallSide.getWall().getRightSideTexture()) != null)
				{
					defaultColorsAndTextures = false;
					break;
				}
			}
			
			if (wallSidesColor != null)
			{
				setWallSidesPaint(RoomPaint.COLORED);
			}
			else if (wallSidesTexture != null)
			{
				setWallSidesPaint(RoomPaint.TEXTURED);
			}
			else if (defaultColorsAndTextures)
			{
				setWallSidesPaint(RoomPaint.DEFAULT);
			}
			else
			{
				setWallSidesPaint(null);
			}
			
			// Search the common floor shininess among rooms
			Float wallSidesShininess = firstWallSide.getSide() == WallSide.LEFT_SIDE
					? firstWallSide.getWall().getLeftSideShininess() : firstWallSide.getWall().getRightSideShininess();
			if (wallSidesShininess != null)
			{
				for (int i = 1; i < wallSides.size(); i++)
				{
					WallSide wallSide = wallSides.get(i);
					if (!wallSidesShininess.equals(wallSide.getSide() == WallSide.LEFT_SIDE
							? wallSide.getWall().getLeftSideShininess() : wallSide.getWall().getRightSideShininess()))
					{
						wallSidesShininess = null;
						break;
					}
				}
			}
			setWallSidesShininess(wallSidesShininess);
			
			Baseboard firstWallSideBaseboard = firstWallSide.getSide() == WallSide.LEFT_SIDE
					? firstWallSide.getWall().getLeftSideBaseboard() : firstWallSide.getWall().getRightSideBaseboard();
			Boolean wallSidesBaseboardVisible = firstWallSideBaseboard != null;
			for (int i = 1; i < wallSides.size(); i++)
			{
				WallSide wallSide = wallSides.get(i);
				if (wallSidesBaseboardVisible != (wallSide.getSide() == WallSide.LEFT_SIDE
						? wallSide.getWall().getLeftSideBaseboard() != null
						: wallSide.getWall().getRightSideBaseboard() != null))
				{
					wallSidesBaseboardVisible = null;
					break;
				}
			}
			getWallSidesBaseboardController().setVisible(wallSidesBaseboardVisible);
			
			// Search the common baseboard thickness  among walls
			Float wallSidesBaseboardThickness = firstWallSideBaseboard != null ? firstWallSideBaseboard.getThickness()
					: this.preferences.getNewWallBaseboardThickness();
			for (int i = 1; i < wallSides.size(); i++)
			{
				WallSide wallSide = wallSides.get(i);
				Baseboard baseboard = wallSide.getSide() == WallSide.LEFT_SIDE
						? wallSide.getWall().getLeftSideBaseboard() : wallSide.getWall().getRightSideBaseboard();
				if (!wallSidesBaseboardThickness.equals(
						baseboard != null ? baseboard.getThickness() : this.preferences.getNewWallBaseboardThickness()))
				{
					wallSidesBaseboardThickness = null;
					break;
				}
			}
			getWallSidesBaseboardController().setThickness(wallSidesBaseboardThickness);
			
			// Search the common baseboard height among walls
			Float wallSidesBaseboardHeight = firstWallSideBaseboard != null ? firstWallSideBaseboard.getHeight()
					: this.preferences.getNewWallBaseboardHeight();
			for (int i = 1; i < wallSides.size(); i++)
			{
				WallSide wallSide = wallSides.get(i);
				Baseboard baseboard = wallSide.getSide() == WallSide.LEFT_SIDE
						? wallSide.getWall().getLeftSideBaseboard() : wallSide.getWall().getRightSideBaseboard();
				if (!wallSidesBaseboardHeight.equals(
						baseboard != null ? baseboard.getHeight() : this.preferences.getNewWallBaseboardHeight()))
				{
					wallSidesBaseboardHeight = null;
					break;
				}
			}
			getWallSidesBaseboardController().setHeight(wallSidesBaseboardHeight);
			
			// Search the maximum height among walls
			float maxBaseboardHeight = firstWallSide.getWall().isTrapezoidal()
					? Math.max(firstWallSide.getWall().getHeight(), firstWallSide.getWall().getHeightAtEnd())
					: firstWallSide.getWall().getHeight();
			for (int i = 1; i < wallSides.size(); i++)
			{
				Wall wall = wallSides.get(i).getWall();
				maxBaseboardHeight = Math.max(maxBaseboardHeight,
						wall.isTrapezoidal() ? Math.max(wall.getHeight(), wall.getHeightAtEnd()) : wall.getHeight());
			}
			getWallSidesBaseboardController().setMaxHeight(maxBaseboardHeight);
			
			// Search the common baseboard color among walls
			Integer wallSidesBaseboardColor = firstWallSideBaseboard != null ? firstWallSideBaseboard.getColor() : null;
			if (wallSidesBaseboardColor != null)
			{
				for (int i = 1; i < wallSides.size(); i++)
				{
					WallSide wallSide = wallSides.get(i);
					Baseboard baseboard = wallSide.getSide() == WallSide.LEFT_SIDE
							? wallSide.getWall().getLeftSideBaseboard() : wallSide.getWall().getRightSideBaseboard();
					if (baseboard == null || !wallSidesBaseboardColor.equals(baseboard.getColor()))
					{
						wallSidesBaseboardColor = null;
						break;
					}
				}
			}
			getWallSidesBaseboardController().setColor(wallSidesBaseboardColor);
			
			// Search the common right baseboard texture among walls
			HomeTexture wallSidesBaseboardTexture = firstWallSideBaseboard != null ? firstWallSideBaseboard.getTexture()
					: null;
			if (wallSidesBaseboardTexture != null)
			{
				for (int i = 1; i < wallSides.size(); i++)
				{
					WallSide wallSide = wallSides.get(i);
					Baseboard baseboard = wallSide.getSide() == WallSide.LEFT_SIDE
							? wallSide.getWall().getLeftSideBaseboard() : wallSide.getWall().getRightSideBaseboard();
					if (baseboard == null || !wallSidesBaseboardTexture.equals(baseboard.getTexture()))
					{
						wallSidesBaseboardTexture = null;
						break;
					}
				}
			}
			getWallSidesBaseboardController().getTextureController().setTexture(wallSidesBaseboardTexture);
			
			defaultColorsAndTextures = true;
			for (int i = 0; i < wallSides.size(); i++)
			{
				WallSide wallSide = wallSides.get(i);
				Baseboard baseboard = wallSide.getSide() == WallSide.LEFT_SIDE
						? wallSide.getWall().getLeftSideBaseboard() : wallSide.getWall().getRightSideBaseboard();
				if (baseboard != null && (baseboard.getColor() != null || baseboard.getTexture() != null))
				{
					defaultColorsAndTextures = false;
					break;
				}
			}
			
			if (wallSidesBaseboardColor != null)
			{
				getWallSidesBaseboardController().setPaint(BaseboardChoiceController.BaseboardPaint.COLORED);
			}
			else if (wallSidesBaseboardTexture != null)
			{
				getWallSidesBaseboardController().setPaint(BaseboardChoiceController.BaseboardPaint.TEXTURED);
			}
			else if (defaultColorsAndTextures)
			{
				getWallSidesBaseboardController().setPaint(BaseboardChoiceController.BaseboardPaint.DEFAULT);
			}
			else
			{
				getWallSidesBaseboardController().setPaint(null);
			}
		}
	}
	
	/**
	 * Returns the wall sides close to each room of <code>rooms</code>.
	 */
	private List<WallSide> getRoomsWallSides(List<Room> rooms, List<WallSide> defaultWallSides)
	{
		List<WallSide> wallSides = new ArrayList<WallSide>();
		for (Room room : rooms)
		{
			Area roomArea = new Area(getPath(room.getPoints(), true));
			if (defaultWallSides != null)
			{
				for (WallSide wallSide : defaultWallSides)
				{
					if (isRoomItersectingWallSide(wallSide.getWall().getPoints(), wallSide.getSide(), roomArea))
					{
						wallSides.add(wallSide);
					}
				}
			}
			else
			{
				for (Wall wall : this.home.getWalls())
				{
					if ((wall.getLevel() == null || wall.getLevel().isViewable())
							&& wall.isAtLevel(this.home.getSelectedLevel()))
					{
						float[][] wallPoints = wall.getPoints();
						if (isRoomItersectingWallSide(wallPoints, WallSide.LEFT_SIDE, roomArea))
						{
							wallSides.add(new WallSide(wall, WallSide.LEFT_SIDE));
						}
						if (isRoomItersectingWallSide(wallPoints, WallSide.RIGHT_SIDE, roomArea))
						{
							wallSides.add(new WallSide(wall, WallSide.RIGHT_SIDE));
						}
					}
				}
			}
		}
		return wallSides;
	}
	
	/**
	 * Returns <code>true</code> if the wall points on the given <code>wallSide</code>
	 * intersects room area.
	 */
	private boolean isRoomItersectingWallSide(float[][] wallPoints, int wallSide, Area roomArea)
	{
		BasicStroke lineStroke = new BasicStroke(2);
		Shape wallSideShape = getWallSideShape(wallPoints, wallSide);
		Area wallSideTestArea = new Area(lineStroke.createStrokedShape(wallSideShape));
		float wallSideTestAreaSurface = getSurface(wallSideTestArea);
		wallSideTestArea.intersect(roomArea);
		if (!wallSideTestArea.isEmpty())
		{
			float wallSideIntersectionSurface = getSurface(wallSideTestArea);
			// Take into account only walls that shares a minimum surface with the room
			if (wallSideIntersectionSurface > wallSideTestAreaSurface * 0.02f)
			{
				return true;
			}
		}
		return false;
	}
	
	/**
	 * Returns the shape of the side of the given <code>wall</code>. 
	 */
	private Shape getWallSideShape(float[][] wallPoints, int wallSide)
	{
		if (wallPoints.length == 4)
		{
			if (wallSide == WallSide.LEFT_SIDE)
			{
				return new Line2D.Float(wallPoints[0][0], wallPoints[0][1], wallPoints[1][0], wallPoints[1][1]);
			}
			else
			{
				return new Line2D.Float(wallPoints[2][0], wallPoints[2][1], wallPoints[3][0], wallPoints[3][1]);
			}
		}
		else
		{
			float[][] wallSidePoints = new float[wallPoints.length / 2][];
			System.arraycopy(wallPoints, wallSide == WallSide.LEFT_SIDE ? 0 : wallSidePoints.length, wallSidePoints, 0,
					wallSidePoints.length);
			return getPath(wallSidePoints, false);
		}
	}
	
	/**
	 * Returns the shape matching the coordinates in <code>points</code> array.
	 */
	private GeneralPath getPath(float[][] points, boolean closedPath)
	{
		GeneralPath path = new GeneralPath();
		path.moveTo(points[0][0], points[0][1]);
		for (int i = 1; i < points.length; i++)
		{
			path.lineTo(points[i][0], points[i][1]);
		}
		if (closedPath)
		{
			path.closePath();
		}
		return path;
	}
	
	/**
	 * Returns the surface of the given <code>area</code>.
	 */
	private float getSurface(Area area)
	{
		// Add the surface of the different polygons of this room
		float surface = 0;
		List<float[]> currentPathPoints = new ArrayList<float[]>();
		for (PathIterator it = area.getPathIterator(null); !it.isDone();)
		{
			float[] roomPoint = new float[2];
			switch (it.currentSegment(roomPoint))
			{
				case PathIterator.SEG_MOVETO:
					currentPathPoints.add(roomPoint);
					break;
				case PathIterator.SEG_LINETO:
					currentPathPoints.add(roomPoint);
					break;
				case PathIterator.SEG_CLOSE:
					float[][] pathPoints = currentPathPoints.toArray(new float[currentPathPoints.size()][]);
					surface += Math.abs(getSignedSurface(pathPoints));
					currentPathPoints.clear();
					break;
			}
			it.next();
		}
		return surface;
	}
	
	private float getSignedSurface(float areaPoints[][])
	{
		// From "Area of a General Polygon" algorithm described in  
		// http://www.davidchandler.com/AreaOfAGeneralPolygon.pdf
		float area = 0;
		for (int i = 1; i < areaPoints.length; i++)
		{
			area += areaPoints[i][0] * areaPoints[i - 1][1];
			area -= areaPoints[i][1] * areaPoints[i - 1][0];
		}
		area += areaPoints[0][0] * areaPoints[areaPoints.length - 1][1];
		area -= areaPoints[0][1] * areaPoints[areaPoints.length - 1][0];
		return area / 2;
	}
	
	/**
	 * Sets the edited name.
	 */
	public void setName(String name)
	{
		if (name != this.name)
		{
			String oldName = this.name;
			this.name = name;
			this.propertyChangeSupport.firePropertyChange(Property.NAME.name(), oldName, name);
		}
	}
	
	/**
	 * Returns the edited name.
	 */
	public String getName()
	{
		return this.name;
	}
	
	/**
	 * Sets whether room area is visible or not.
	 */
	public void setAreaVisible(Boolean areaVisible)
	{
		if (areaVisible != this.areaVisible)
		{
			Boolean oldAreaVisible = this.areaVisible;
			this.areaVisible = areaVisible;
			this.propertyChangeSupport.firePropertyChange(Property.AREA_VISIBLE.name(), oldAreaVisible, areaVisible);
		}
	}
	
	/**
	 * Returns whether room area is visible or not.
	 */
	public Boolean getAreaVisible()
	{
		return this.areaVisible;
	}
	
	/**
	 * Sets whether room floor is visible or not.
	 */
	public void setFloorVisible(Boolean floorVisible)
	{
		if (floorVisible != this.floorVisible)
		{
			Boolean oldFloorVisible = this.floorVisible;
			this.floorVisible = floorVisible;
			this.propertyChangeSupport.firePropertyChange(Property.FLOOR_VISIBLE.name(), oldFloorVisible, floorVisible);
		}
	}
	
	/**
	 * Returns whether room floor is visible or not.
	 */
	public Boolean getFloorVisible()
	{
		return this.floorVisible;
	}
	
	/**
	 * Sets the edited color of the floor.
	 */
	public void setFloorColor(Integer floorColor)
	{
		if (floorColor != this.floorColor)
		{
			Integer oldFloorColor = this.floorColor;
			this.floorColor = floorColor;
			this.propertyChangeSupport.firePropertyChange(Property.FLOOR_COLOR.name(), oldFloorColor, floorColor);
			
			setFloorPaint(RoomPaint.COLORED);
		}
	}
	
	/**
	 * Returns the edited color of the floor.
	 */
	public Integer getFloorColor()
	{
		return this.floorColor;
	}
	
	/**
	 * Sets whether the floor is colored, textured or unknown painted.
	 */
	public void setFloorPaint(RoomPaint floorPaint)
	{
		if (floorPaint != this.floorPaint)
		{
			RoomPaint oldFloorPaint = this.floorPaint;
			this.floorPaint = floorPaint;
			this.propertyChangeSupport.firePropertyChange(Property.FLOOR_PAINT.name(), oldFloorPaint, floorPaint);
		}
	}
	
	/**
	 * Returns whether the floor is colored, textured or unknown painted.
	 */
	public RoomPaint getFloorPaint()
	{
		return this.floorPaint;
	}
	
	/**
	 * Sets the edited shininess of the floor.
	 */
	public void setFloorShininess(Float floorShininess)
	{
		if (floorShininess != this.floorShininess)
		{
			Float oldFloorShininess = this.floorShininess;
			this.floorShininess = floorShininess;
			this.propertyChangeSupport.firePropertyChange(Property.FLOOR_SHININESS.name(), oldFloorShininess,
					floorShininess);
		}
	}
	
	/**
	 * Returns the edited shininess of the floor.
	 */
	public Float getFloorShininess()
	{
		return this.floorShininess;
	}
	
	/**
	 * Sets whether room ceiling is visible or not.
	 */
	public void setCeilingVisible(Boolean ceilingCeilingVisible)
	{
		if (ceilingCeilingVisible != this.ceilingVisible)
		{
			Boolean oldCeilingVisible = this.ceilingVisible;
			this.ceilingVisible = ceilingCeilingVisible;
			this.propertyChangeSupport.firePropertyChange(Property.CEILING_VISIBLE.name(), oldCeilingVisible,
					ceilingCeilingVisible);
		}
	}
	
	/**
	 * Returns whether room ceiling is ceilingCeilingVisible or not.
	 */
	public Boolean getCeilingVisible()
	{
		return this.ceilingVisible;
	}
	
	/**
	 * Sets the edited color of the ceiling.
	 */
	public void setCeilingColor(Integer ceilingColor)
	{
		if (ceilingColor != this.ceilingColor)
		{
			Integer oldCeilingColor = this.ceilingColor;
			this.ceilingColor = ceilingColor;
			this.propertyChangeSupport.firePropertyChange(Property.CEILING_COLOR.name(), oldCeilingColor, ceilingColor);
			
			setCeilingPaint(RoomPaint.COLORED);
		}
	}
	
	/**
	 * Returns the edited color of the ceiling.
	 */
	public Integer getCeilingColor()
	{
		return this.ceilingColor;
	}
	
	/**
	 * Sets whether the ceiling is colored, textured or unknown painted.
	 */
	public void setCeilingPaint(RoomPaint ceilingPaint)
	{
		if (ceilingPaint != this.ceilingPaint)
		{
			RoomPaint oldCeilingPaint = this.ceilingPaint;
			this.ceilingPaint = ceilingPaint;
			this.propertyChangeSupport.firePropertyChange(Property.CEILING_PAINT.name(), oldCeilingPaint, ceilingPaint);
		}
	}
	
	/**
	 * Returns whether the ceiling is colored, textured or unknown painted.
	 */
	public RoomPaint getCeilingPaint()
	{
		return this.ceilingPaint;
	}
	
	/**
	 * Sets the edited shininess of the ceiling.
	 */
	public void setCeilingShininess(Float ceilingShininess)
	{
		if (ceilingShininess != this.ceilingShininess)
		{
			Float oldCeilingShininess = this.ceilingShininess;
			this.ceilingShininess = ceilingShininess;
			this.propertyChangeSupport.firePropertyChange(Property.CEILING_SHININESS.name(), oldCeilingShininess,
					ceilingShininess);
		}
	}
	
	/**
	 * Returns the edited shininess of the ceiling.
	 */
	public Float getCeilingShininess()
	{
		return this.ceilingShininess;
	}
	
	/**
	 * Returns <code>true</code> if walls around the edited rooms should be split.
	 * @since 4.0
	 */
	public boolean isSplitSurroundingWalls()
	{
		return this.splitSurroundingWalls;
	}
	
	/**
	 * Sets whether walls around the edited rooms should be split or not.
	 * @since 4.0
	 */
	public void setSplitSurroundingWalls(boolean splitSurroundingWalls)
	{
		if (splitSurroundingWalls != this.splitSurroundingWalls)
		{
			this.splitSurroundingWalls = splitSurroundingWalls;
			this.propertyChangeSupport.firePropertyChange(Property.SPLIT_SURROUNDING_WALLS.name(),
					!splitSurroundingWalls, splitSurroundingWalls);
		}
	}
	
	/**
	 * Returns <code>true</code> if walls around the edited rooms need to be split 
	 * to avoid changing the color of wall sides that belong to neighborhood rooms.
	 * @since 4.0
	 */
	public boolean isSplitSurroundingWallsNeeded()
	{
		return this.splitSurroundingWallsNeeded;
	}
	
	/**
	 * Sets the edited color of the wall sides.
	 * @since 4.0
	 */
	public void setWallSidesColor(Integer wallSidesColor)
	{
		if (wallSidesColor != this.wallSidesColor)
		{
			Integer oldWallSidesColor = this.wallSidesColor;
			this.wallSidesColor = wallSidesColor;
			this.propertyChangeSupport.firePropertyChange(Property.WALL_SIDES_COLOR.name(), oldWallSidesColor,
					wallSidesColor);
			
			setWallSidesPaint(RoomPaint.COLORED);
		}
	}
	
	/**
	 * Returns the edited color of the wall sides.
	 * @since 4.0
	 */
	public Integer getWallSidesColor()
	{
		return this.wallSidesColor;
	}
	
	/**
	 * Sets whether the wall sides are colored, textured or unknown painted.
	 * @since 4.0
	 */
	public void setWallSidesPaint(RoomPaint wallSidesPaint)
	{
		if (wallSidesPaint != this.wallSidesPaint)
		{
			RoomPaint oldWallSidesPaint = this.wallSidesPaint;
			this.wallSidesPaint = wallSidesPaint;
			this.propertyChangeSupport.firePropertyChange(Property.WALL_SIDES_PAINT.name(), oldWallSidesPaint,
					wallSidesPaint);
		}
	}
	
	/**
	 * Returns whether the wall sides are colored, textured or unknown painted.
	 * @since 4.0
	 */
	public RoomPaint getWallSidesPaint()
	{
		return this.wallSidesPaint;
	}
	
	/**
	 * Sets the edited shininess of the wall sides.
	 * @since 4.0
	 */
	public void setWallSidesShininess(Float wallSidesShininess)
	{
		if (wallSidesShininess != this.wallSidesShininess)
		{
			Float oldWallSidesShininess = this.wallSidesShininess;
			this.wallSidesShininess = wallSidesShininess;
			this.propertyChangeSupport.firePropertyChange(Property.WALL_SIDES_SHININESS.name(), oldWallSidesShininess,
					wallSidesShininess);
		}
	}
	
	/**
	 * Returns the edited shininess of the wall sides.
	 * @since 4.0
	 */
	public Float getWallSidesShininess()
	{
		return this.wallSidesShininess;
	}
	
	/**
	 * Controls the modification of selected rooms in edited home.
	 */
	public void modifyRooms()
	{
		List<Selectable> oldSelection = this.home.getSelectedItems();
		List<Room> selectedRooms = Home.getRoomsSubList(oldSelection);
		if (!selectedRooms.isEmpty())
		{
			String name = getName();
			Boolean areaVisible = getAreaVisible();
			Boolean floorVisible = getFloorVisible();
			RoomPaint floorPaint = getFloorPaint();
			Integer floorColor = floorPaint == RoomPaint.COLORED ? getFloorColor() : null;
			HomeTexture floorTexture = floorPaint == RoomPaint.TEXTURED ? getFloorTextureController().getTexture()
					: null;
			Float floorShininess = getFloorShininess();
			Boolean ceilingVisible = getCeilingVisible();
			RoomPaint ceilingPaint = getCeilingPaint();
			Integer ceilingColor = ceilingPaint == RoomPaint.COLORED ? getCeilingColor() : null;
			HomeTexture ceilingTexture = ceilingPaint == RoomPaint.TEXTURED ? getCeilingTextureController().getTexture()
					: null;
			Float ceilingShininess = getCeilingShininess();
			Integer wallSidesColor = getWallSidesPaint() == RoomPaint.COLORED ? getWallSidesColor() : null;
			HomeTexture wallSidesTexture = getWallSidesPaint() == RoomPaint.TEXTURED
					? getWallSidesTextureController().getTexture() : null;
			Float wallSidesShininess = getWallSidesShininess();
			Boolean wallSidesBaseboardVisible = getWallSidesBaseboardController().getVisible();
			Float wallSidesBaseboardThickness = getWallSidesBaseboardController().getThickness();
			Float wallSidesBaseboardHeight = getWallSidesBaseboardController().getHeight();
			BaseboardChoiceController.BaseboardPaint wallSidesBaseboardPaint = getWallSidesBaseboardController()
					.getPaint();
			Integer wallSidesBaseboardColor = wallSidesBaseboardPaint == BaseboardChoiceController.BaseboardPaint.COLORED
					? getWallSidesBaseboardController().getColor() : null;
			HomeTexture wallSidesBaseboardTexture = wallSidesBaseboardPaint == BaseboardChoiceController.BaseboardPaint.TEXTURED
					? getWallSidesBaseboardController().getTextureController().getTexture() : null;
			List<WallSide> selectedRoomsWallSides = getRoomsWallSides(selectedRooms, null);
			
			// Create an array of modified rooms with their current properties values
			ModifiedRoom[] modifiedRooms = new ModifiedRoom[selectedRooms.size()];
			for (int i = 0; i < modifiedRooms.length; i++)
			{
				modifiedRooms[i] = new ModifiedRoom(selectedRooms.get(i));
			}
			
			// Apply modification
			List<ModifiedWall> deletedWalls = new ArrayList<ModifiedWall>();
			List<ModifiedWall> addedWalls = new ArrayList<ModifiedWall>();
			List<Selectable> newSelection = new ArrayList<Selectable>(oldSelection);
			if (this.splitSurroundingWalls)
			{
				if (splitWalls(selectedRoomsWallSides, deletedWalls, addedWalls, newSelection))
				{
					this.home.setSelectedItems(newSelection);
					// Update wall sides
					selectedRoomsWallSides = getRoomsWallSides(selectedRooms, selectedRoomsWallSides);
				}
			}
			
			// Create an array of modified wall sides with their current properties values
			ModifiedWallSide[] modifiedWallSides = new ModifiedWallSide[selectedRoomsWallSides.size()];
			for (int i = 0; i < modifiedWallSides.length; i++)
			{
				modifiedWallSides[i] = new ModifiedWallSide(selectedRoomsWallSides.get(i));
			}
			doModifyRoomsAndWallSides(home, modifiedRooms, name, areaVisible, floorVisible, floorPaint, floorColor,
					floorTexture, floorShininess, ceilingVisible, ceilingPaint, ceilingColor, ceilingTexture,
					ceilingShininess, modifiedWallSides, this.preferences.getNewWallBaseboardThickness(),
					this.preferences.getNewWallBaseboardHeight(), wallSidesColor, wallSidesTexture, wallSidesShininess,
					wallSidesBaseboardVisible, wallSidesBaseboardThickness, wallSidesBaseboardHeight,
					wallSidesBaseboardPaint, wallSidesBaseboardColor, wallSidesBaseboardTexture, null, null);
			if (this.undoSupport != null)
			{
				UndoableEdit undoableEdit = new RoomsAndWallSidesModificationUndoableEdit(this.home, this.preferences,
						oldSelection, newSelection, modifiedRooms, name, areaVisible, floorVisible, floorPaint,
						floorColor, floorTexture, floorShininess, ceilingVisible, ceilingPaint, ceilingColor,
						ceilingTexture, ceilingShininess, modifiedWallSides,
						this.preferences.getNewWallBaseboardThickness(), this.preferences.getNewWallBaseboardHeight(),
						wallSidesColor, wallSidesTexture, wallSidesShininess, wallSidesBaseboardVisible,
						wallSidesBaseboardThickness, wallSidesBaseboardHeight, wallSidesBaseboardPaint,
						wallSidesBaseboardColor, wallSidesBaseboardTexture,
						deletedWalls.toArray(new ModifiedWall[deletedWalls.size()]),
						addedWalls.toArray(new ModifiedWall[addedWalls.size()]));
				this.undoSupport.postEdit(undoableEdit);
			}
			if (name != null)
			{
				this.preferences.addAutoCompletionString("RoomName", name);
			}
		}
	}
	
	/**
	 * Splits walls that overfill on other rooms if needed and returns <code>false</code> if the operation wasn't needed. 
	 */
	private boolean splitWalls(List<WallSide> wallSides, List<ModifiedWall> deletedWalls, List<ModifiedWall> addedWalls,
			List<Selectable> selectedItems)
	{
		Map<Wall, ModifiedWall> existingWalls = null;
		List<Wall> newWalls = new ArrayList<Wall>();
		WallSide splitWallSide;
		do
		{
			splitWallSide = null;
			Wall firstWall = null;
			Wall secondWall = null;
			ModifiedWall deletedWall = null;
			for (Iterator<WallSide> it = wallSides.iterator(); it.hasNext() && splitWallSide == null;)
			{
				WallSide wallSide = (WallSide) it.next();
				Wall wall = wallSide.getWall();
				Float arcExtent = wall.getArcExtent();
				if (arcExtent == null || arcExtent.floatValue() == 0)
				{ // Ignore round walls
					Area wallArea = new Area(getPath(wall.getPoints(), true));
					for (WallSide intersectedWallSide : wallSides)
					{
						Wall intersectedWall = intersectedWallSide.getWall();
						if (wall != intersectedWall)
						{
							Area intersectedWallArea = new Area(getPath(intersectedWall.getPoints(), true));
							intersectedWallArea.intersect(wallArea);
							if (!intersectedWallArea.isEmpty() && intersectedWallArea.isSingular())
							{
								float[] intersection = computeIntersection(wall.getXStart(), wall.getYStart(),
										wall.getXEnd(), wall.getYEnd(), intersectedWall.getXStart(),
										intersectedWall.getYStart(), intersectedWall.getXEnd(),
										intersectedWall.getYEnd());
								if (intersection != null)
								{
									// Clone new walls to copy their characteristics 
									firstWall = wall.clone();
									secondWall = wall.clone();
									// Store wall level to add new walls at the same level as the split wall
									firstWall.setLevel(wall.getLevel());
									secondWall.setLevel(wall.getLevel());
									
									// Change split walls end and start point
									firstWall.setXEnd(intersection[0]);
									firstWall.setYEnd(intersection[1]);
									secondWall.setXStart(intersection[0]);
									secondWall.setYStart(intersection[1]);
									
									if (firstWall.getLength() > intersectedWall.getThickness() / 2
											&& secondWall.getLength() > intersectedWall.getThickness() / 2)
									{
										// If method is called for test purpose
										if (deletedWalls == null)
										{
											return true;
										}
										
										if (existingWalls == null)
										{
											// Store all walls at start and end in case there would be more than one change on a wall   
											existingWalls = new HashMap<Wall, ModifiedWall>(wallSides.size());
											for (WallSide side : wallSides)
											{
												if (!existingWalls.containsKey(side.getWall()))
												{
													existingWalls.put(side.getWall(), new ModifiedWall(side.getWall()));
												}
											}
										}
										
										deletedWall = existingWalls.get(wall);
										Wall wallAtStart = wall.getWallAtStart();
										if (wallAtStart != null)
										{
											firstWall.setWallAtStart(wallAtStart);
											if (wallAtStart.getWallAtEnd() == wall)
											{
												wallAtStart.setWallAtEnd(firstWall);
											}
											else
											{
												wallAtStart.setWallAtStart(firstWall);
											}
										}
										
										Wall wallAtEnd = wall.getWallAtEnd();
										if (wallAtEnd != null)
										{
											secondWall.setWallAtEnd(wallAtEnd);
											if (wallAtEnd.getWallAtEnd() == wall)
											{
												wallAtEnd.setWallAtEnd(secondWall);
											}
											else
											{
												wallAtEnd.setWallAtStart(secondWall);
											}
										}
										
										firstWall.setWallAtEnd(secondWall);
										secondWall.setWallAtStart(firstWall);
										
										if (wall.getHeightAtEnd() != null)
										{
											Float heightAtIntersecion = wall.getHeight()
													+ (wall.getHeightAtEnd() - wall.getHeight())
															* (float) Point2D.distance(wall.getXStart(),
																	wall.getYStart(), intersection[0], intersection[1])
															/ wall.getLength();
											firstWall.setHeightAtEnd(heightAtIntersecion);
											secondWall.setHeight(heightAtIntersecion);
										}
										
										splitWallSide = wallSide;
										break;
									}
								}
							}
						}
					}
				}
			}
			
			if (splitWallSide != null)
			{
				newWalls.add(firstWall);
				newWalls.add(secondWall);
				Wall splitWall = splitWallSide.getWall();
				if (this.home.getWalls().contains(splitWall))
				{
					deletedWalls.add(deletedWall);
				}
				else
				{
					// Remove from newWalls in case it was a wall split twice
					for (Iterator<Wall> it = newWalls.iterator(); it.hasNext();)
					{
						if (it.next() == splitWall)
						{
							it.remove();
							break;
						}
					}
				}
				// Update selected items
				if (selectedItems.remove(splitWall))
				{
					selectedItems.add(firstWall);
					selectedItems.add(secondWall);
				}
				
				wallSides.remove(splitWallSide);
				wallSides.add(new WallSide(firstWall, splitWallSide.getSide()));
				wallSides.add(new WallSide(secondWall, splitWallSide.getSide()));
				// Update any wall side that reference the same wall
				List<WallSide> sameWallSides = new ArrayList<WallSide>();
				for (Iterator<WallSide> it = wallSides.iterator(); it.hasNext();)
				{
					WallSide wallSide = it.next();
					if (wallSide.getWall() == splitWall)
					{
						it.remove();
						sameWallSides.add(new WallSide(firstWall, wallSide.getSide()));
						sameWallSides.add(new WallSide(secondWall, wallSide.getSide()));
					}
				}
				wallSides.addAll(sameWallSides);
			}
		} while (splitWallSide != null);
		
		// If method is called for test purpose
		if (deletedWalls == null)
		{
			return false;
		}
		else
		{
			for (Wall newWall : newWalls)
			{
				ModifiedWall addedWall = new ModifiedWall(newWall);
				addedWalls.add(addedWall);
				this.home.addWall(newWall);
				newWall.setLevel(addedWall.getLevel());
			}
			for (ModifiedWall deletedWall : deletedWalls)
			{
				this.home.deleteWall(deletedWall.getWall());
			}
			return !deletedWalls.isEmpty();
		}
	}
	
	/** 
	 * Returns the intersection between a line segment and a second line.
	 */
	private float[] computeIntersection(float xPoint1, float yPoint1, float xPoint2, float yPoint2, float xPoint3,
			float yPoint3, float xPoint4, float yPoint4)
	{
		float[] point = PlanController.computeIntersection(xPoint1, yPoint1, xPoint2, yPoint2, xPoint3, yPoint3,
				xPoint4, yPoint4);
		if (Line2D.ptSegDistSq(xPoint1, yPoint1, xPoint2, yPoint2, point[0], point[1]) < 1E-7
				&& (Math.abs(xPoint1 - point[0]) > 1E-4 || Math.abs(yPoint1 - point[1]) > 1E-4)
				&& (Math.abs(xPoint2 - point[0]) > 1E-4 || Math.abs(yPoint2 - point[1]) > 1E-4))
		{
			return point;
		}
		else
		{
			return null;
		}
	}
	
	/**
	 * Undoable edit for rooms modification. This class isn't anonymous to avoid
	 * being bound to controller and its view.
	 */
	private static class RoomsAndWallSidesModificationUndoableEdit extends AbstractUndoableEdit
	{
		private final Home home;
		private final UserPreferences preferences;
		private final List<Selectable> oldSelection;
		private final List<Selectable> newSelection;
		private final ModifiedRoom[] modifiedRooms;
		private final String name;
		private final Boolean areaVisible;
		private final Boolean floorVisible;
		private final RoomPaint floorPaint;
		private final Integer floorColor;
		private final HomeTexture floorTexture;
		private final Float floorShininess;
		private final Boolean ceilingVisible;
		private final RoomPaint ceilingPaint;
		private final Integer ceilingColor;
		private final HomeTexture ceilingTexture;
		private final Float ceilingShininess;
		private final ModifiedWallSide[] modifiedWallSides;
		private final float newWallBaseboardHeight;
		private final float newWallBaseboardThickness;
		private final Integer wallSidesColor;
		private final HomeTexture wallSidesTexture;
		private final Float wallSidesShininess;
		private final Boolean wallSidesBaseboardVisible;
		private final Float wallSidesBaseboardThickness;
		private final Float wallSidesBaseboardHeight;
		private final BaseboardChoiceController.BaseboardPaint wallSidesBaseboardPaint;
		private final Integer wallSidesBaseboardColor;
		private final HomeTexture wallSidesBaseboardTexture;
		private final ModifiedWall[] deletedWalls;
		private final ModifiedWall[] addedWalls;
		
		private RoomsAndWallSidesModificationUndoableEdit(Home home, UserPreferences preferences,
				List<Selectable> oldSelection, List<Selectable> newSelection, ModifiedRoom[] modifiedRooms, String name,
				Boolean areaVisible, Boolean floorVisible, RoomPaint floorPaint, Integer floorColor,
				HomeTexture floorTexture, Float floorShininess, Boolean ceilingVisible, RoomPaint ceilingPaint,
				Integer ceilingColor, HomeTexture ceilingTexture, Float ceilingShininess,
				ModifiedWallSide[] modifiedWallSides, float newWallBaseboardThickness, float newWallBaseboardHeight,
				Integer wallSidesColor, HomeTexture wallSidesTexture, Float wallSidesShininess,
				Boolean wallSidesBaseboardVisible, Float wallSidesBaseboardThickness, Float wallSidesBaseboardHeight,
				BaseboardChoiceController.BaseboardPaint wallSidesBaseboardPaint, Integer wallSidesBaseboardColor,
				HomeTexture wallSidesBaseboardTexture, ModifiedWall[] deletedWalls, ModifiedWall[] addedWalls)
		{
			this.home = home;
			this.preferences = preferences;
			this.oldSelection = oldSelection;
			this.newSelection = newSelection;
			this.modifiedRooms = modifiedRooms;
			this.name = name;
			this.areaVisible = areaVisible;
			this.floorVisible = floorVisible;
			this.floorPaint = floorPaint;
			this.floorColor = floorColor;
			this.floorTexture = floorTexture;
			this.floorShininess = floorShininess;
			this.ceilingVisible = ceilingVisible;
			this.ceilingPaint = ceilingPaint;
			this.ceilingColor = ceilingColor;
			this.ceilingTexture = ceilingTexture;
			this.ceilingShininess = ceilingShininess;
			this.modifiedWallSides = modifiedWallSides;
			this.newWallBaseboardThickness = newWallBaseboardThickness;
			this.newWallBaseboardHeight = newWallBaseboardHeight;
			this.wallSidesColor = wallSidesColor;
			this.wallSidesTexture = wallSidesTexture;
			this.wallSidesShininess = wallSidesShininess;
			this.wallSidesBaseboardVisible = wallSidesBaseboardVisible;
			this.wallSidesBaseboardThickness = wallSidesBaseboardThickness;
			this.wallSidesBaseboardHeight = wallSidesBaseboardHeight;
			this.wallSidesBaseboardPaint = wallSidesBaseboardPaint;
			this.wallSidesBaseboardColor = wallSidesBaseboardColor;
			this.wallSidesBaseboardTexture = wallSidesBaseboardTexture;
			this.deletedWalls = deletedWalls;
			this.addedWalls = addedWalls;
		}
		
		@Override
		public void undo() throws CannotUndoException
		{
			super.undo();
			undoModifyRoomsAndWallSides(this.home, this.modifiedRooms, this.modifiedWallSides, this.deletedWalls,
					this.addedWalls);
			this.home.setSelectedItems(this.oldSelection);
		}
		
		@Override
		public void redo() throws CannotRedoException
		{
			super.redo();
			doModifyRoomsAndWallSides(this.home, this.modifiedRooms, this.name, this.areaVisible, this.floorVisible,
					this.floorPaint, this.floorColor, this.floorTexture, this.floorShininess, this.ceilingVisible,
					this.ceilingPaint, this.ceilingColor, this.ceilingTexture, this.ceilingShininess,
					this.modifiedWallSides, newWallBaseboardThickness, this.newWallBaseboardHeight, this.wallSidesColor,
					this.wallSidesTexture, this.wallSidesShininess, this.wallSidesBaseboardVisible,
					this.wallSidesBaseboardThickness, this.wallSidesBaseboardHeight, this.wallSidesBaseboardPaint,
					this.wallSidesBaseboardColor, this.wallSidesBaseboardTexture, this.deletedWalls, this.addedWalls);
			this.home.setSelectedItems(this.newSelection);
		}
		
		@Override
		public String getPresentationName()
		{
			return this.preferences.getLocalizedString(RoomController.class, "undoModifyRoomsName");
		}
	}
	
	/**
	 * Modifies rooms and walls properties with the values in parameter.
	 */
	private static void doModifyRoomsAndWallSides(Home home, ModifiedRoom[] modifiedRooms, String name,
			Boolean areaVisible, Boolean floorVisible, RoomPaint floorPaint, Integer floorColor,
			HomeTexture floorTexture, Float floorShininess, Boolean ceilingVisible, RoomPaint ceilingPaint,
			Integer ceilingColor, HomeTexture ceilingTexture, Float ceilingShininess,
			ModifiedWallSide[] modifiedWallSides, float newWallBaseboardThickness, float newWallBaseboardHeight,
			Integer wallSidesColor, HomeTexture wallSidesTexture, Float wallSidesShininess,
			Boolean wallSidesBaseboardVisible, Float wallSidesBaseboardThickness, Float wallSidesBaseboardHeight,
			BaseboardChoiceController.BaseboardPaint wallSidesBaseboardPaint, Integer wallSidesBaseboardColor,
			HomeTexture wallSidesBaseboardTexture, ModifiedWall[] deletedWalls, ModifiedWall[] addedWalls)
	{
		if (deletedWalls != null)
		{
			for (ModifiedWall newWall : addedWalls)
			{
				newWall.resetJoinedWalls();
				home.addWall(newWall.getWall());
				newWall.getWall().setLevel(newWall.getLevel());
			}
			for (ModifiedWall deletedWall : deletedWalls)
			{
				home.deleteWall(deletedWall.getWall());
			}
		}
		for (ModifiedRoom modifiedRoom : modifiedRooms)
		{
			Room room = modifiedRoom.getRoom();
			if (name != null)
			{
				room.setName(name);
			}
			if (areaVisible != null)
			{
				room.setAreaVisible(areaVisible);
			}
			if (floorVisible != null)
			{
				room.setFloorVisible(floorVisible);
			}
			if (floorPaint != null)
			{
				switch (floorPaint)
				{
					case DEFAULT:
						room.setFloorColor(null);
						room.setFloorTexture(null);
						break;
					case COLORED:
						room.setFloorColor(floorColor);
						room.setFloorTexture(null);
						break;
					case TEXTURED:
						room.setFloorColor(null);
						room.setFloorTexture(floorTexture);
						break;
				}
			}
			if (floorShininess != null)
			{
				room.setFloorShininess(floorShininess);
			}
			if (ceilingVisible != null)
			{
				room.setCeilingVisible(ceilingVisible);
			}
			if (ceilingPaint != null)
			{
				switch (ceilingPaint)
				{
					case DEFAULT:
						room.setCeilingColor(null);
						room.setCeilingTexture(null);
						break;
					case COLORED:
						room.setCeilingColor(ceilingColor);
						room.setCeilingTexture(null);
						break;
					case TEXTURED:
						room.setCeilingColor(null);
						room.setCeilingTexture(ceilingTexture);
						break;
				}
			}
			if (ceilingShininess != null)
			{
				room.setCeilingShininess(ceilingShininess);
			}
		}
		for (ModifiedWallSide modifiedWallSide : modifiedWallSides)
		{
			WallSide wallSide = modifiedWallSide.getWallSide();
			Wall wall = wallSide.getWall();
			if (wallSidesColor != null)
			{
				if (wallSide.getSide() == WallSide.LEFT_SIDE)
				{
					wall.setLeftSideColor(wallSidesColor);
				}
				else
				{
					wall.setRightSideColor(wallSidesColor);
				}
			}
			
			if (wallSidesTexture != null || wallSidesColor != null)
			{
				if (wallSide.getSide() == WallSide.LEFT_SIDE)
				{
					wall.setLeftSideTexture(wallSidesTexture);
				}
				else
				{
					wall.setRightSideTexture(wallSidesTexture);
				}
			}
			
			if (wallSidesShininess != null)
			{
				if (wallSide.getSide() == WallSide.LEFT_SIDE)
				{
					wall.setLeftSideShininess(wallSidesShininess);
				}
				else
				{
					wall.setRightSideShininess(wallSidesShininess);
				}
			}
			
			if (wallSidesBaseboardVisible == Boolean.FALSE)
			{
				if (wallSide.getSide() == WallSide.LEFT_SIDE)
				{
					wall.setLeftSideBaseboard(null);
				}
				else
				{
					wall.setRightSideBaseboard(null);
				}
			}
			else
			{
				Baseboard baseboard = wallSide.getSide() == WallSide.LEFT_SIDE ? wall.getLeftSideBaseboard()
						: wall.getRightSideBaseboard();
				if (wallSidesBaseboardVisible == Boolean.TRUE || baseboard != null)
				{
					float baseboardThickness = baseboard != null ? baseboard.getThickness() : newWallBaseboardThickness;
					float baseboardHeight = baseboard != null ? baseboard.getHeight() : newWallBaseboardHeight;
					Integer baseboardColor = baseboard != null ? baseboard.getColor() : null;
					HomeTexture baseboardTexture = baseboard != null ? baseboard.getTexture() : null;
					if (wallSidesBaseboardPaint != null)
					{
						switch (wallSidesBaseboardPaint)
						{
							case DEFAULT:
								baseboardColor = null;
								baseboardTexture = null;
								break;
							case COLORED:
								if (wallSidesBaseboardColor != null)
								{
									baseboardColor = wallSidesBaseboardColor;
								}
								baseboardTexture = null;
								break;
							case TEXTURED:
								baseboardColor = null;
								if (wallSidesBaseboardTexture != null)
								{
									baseboardTexture = wallSidesBaseboardTexture;
								}
								break;
						}
					}
					baseboard = Baseboard.getInstance(
							wallSidesBaseboardThickness != null ? wallSidesBaseboardThickness : baseboardThickness,
							wallSidesBaseboardHeight != null ? wallSidesBaseboardHeight : baseboardHeight,
							baseboardColor, baseboardTexture);
					if (wallSide.getSide() == WallSide.LEFT_SIDE)
					{
						wall.setLeftSideBaseboard(baseboard);
					}
					else
					{
						wall.setRightSideBaseboard(baseboard);
					}
				}
			}
		}
	}
	
	/**
	 * Restores room properties from the values stored in <code>modifiedRooms</code> and <code>modifiedWallSides</code>.
	 */
	private static void undoModifyRoomsAndWallSides(Home home, ModifiedRoom[] modifiedRooms,
			ModifiedWallSide[] modifiedWallSides, ModifiedWall[] deletedWalls, ModifiedWall[] addedWalls)
	{
		for (ModifiedRoom modifiedRoom : modifiedRooms)
		{
			modifiedRoom.reset();
		}
		for (ModifiedWallSide modifiedWallSide : modifiedWallSides)
		{
			modifiedWallSide.reset();
		}
		for (ModifiedWall newWall : addedWalls)
		{
			home.deleteWall(newWall.getWall());
		}
		for (ModifiedWall deletedWall : deletedWalls)
		{
			deletedWall.resetJoinedWalls();
			home.addWall(deletedWall.getWall());
			deletedWall.getWall().setLevel(deletedWall.getLevel());
		}
	}
	
	/**
	 * Stores the current properties values of a modified room.
	 */
	private static final class ModifiedRoom
	{
		private final Room room;
		private final String name;
		private final boolean areaVisible;
		private final boolean floorVisible;
		private final Integer floorColor;
		private final HomeTexture floorTexture;
		private final float floorShininess;
		private final boolean ceilingVisible;
		private final Integer ceilingColor;
		private final HomeTexture ceilingTexture;
		private final float ceilingShininess;
		
		public ModifiedRoom(Room room)
		{
			this.room = room;
			this.name = room.getName();
			this.areaVisible = room.isAreaVisible();
			this.floorVisible = room.isFloorVisible();
			this.floorColor = room.getFloorColor();
			this.floorTexture = room.getFloorTexture();
			this.floorShininess = room.getFloorShininess();
			this.ceilingVisible = room.isCeilingVisible();
			this.ceilingColor = room.getCeilingColor();
			this.ceilingTexture = room.getCeilingTexture();
			this.ceilingShininess = room.getCeilingShininess();
		}
		
		public Room getRoom()
		{
			return this.room;
		}
		
		public void reset()
		{
			this.room.setName(this.name);
			this.room.setAreaVisible(this.areaVisible);
			this.room.setFloorVisible(this.floorVisible);
			this.room.setFloorColor(this.floorColor);
			this.room.setFloorTexture(this.floorTexture);
			this.room.setFloorShininess(this.floorShininess);
			this.room.setCeilingVisible(this.ceilingVisible);
			this.room.setCeilingColor(this.ceilingColor);
			this.room.setCeilingTexture(this.ceilingTexture);
			this.room.setCeilingShininess(this.ceilingShininess);
		}
	}
	
	/**
	 * A wall side.  
	 */
	private class WallSide
	{
		public static final int LEFT_SIDE = 0;
		public static final int RIGHT_SIDE = 1;
		
		private Wall wall;
		private int side;
		private final Wall wallAtStart;
		private final Wall wallAtEnd;
		private final boolean joinedAtEndOfWallAtStart;
		private final boolean joinedAtStartOfWallAtEnd;
		
		public WallSide(Wall wall, int side)
		{
			this.wall = wall;
			this.side = side;
			this.wallAtStart = wall.getWallAtStart();
			this.joinedAtEndOfWallAtStart = this.wallAtStart != null && this.wallAtStart.getWallAtEnd() == wall;
			this.wallAtEnd = wall.getWallAtEnd();
			this.joinedAtStartOfWallAtEnd = this.wallAtEnd != null && wallAtEnd.getWallAtStart() == wall;
		}
		
		public Wall getWall()
		{
			return this.wall;
		}
		
		public int getSide()
		{
			return this.side;
		}
		
		public Wall getWallAtStart()
		{
			return this.wallAtStart;
		}
		
		public Wall getWallAtEnd()
		{
			return this.wallAtEnd;
		}
		
		public boolean isJoinedAtEndOfWallAtStart()
		{
			return this.joinedAtEndOfWallAtStart;
		}
		
		public boolean isJoinedAtStartOfWallAtEnd()
		{
			return this.joinedAtStartOfWallAtEnd;
		}
	}
	
	/**
	 * A modified wall.  
	 */
	private class ModifiedWall
	{
		private Wall wall;
		private final Level level;
		private final Wall wallAtStart;
		private final Wall wallAtEnd;
		private final boolean joinedAtEndOfWallAtStart;
		private final boolean joinedAtStartOfWallAtEnd;
		
		public ModifiedWall(Wall wall)
		{
			this.wall = wall;
			this.level = wall.getLevel();
			this.wallAtStart = wall.getWallAtStart();
			this.joinedAtEndOfWallAtStart = this.wallAtStart != null && this.wallAtStart.getWallAtEnd() == wall;
			this.wallAtEnd = wall.getWallAtEnd();
			this.joinedAtStartOfWallAtEnd = this.wallAtEnd != null && wallAtEnd.getWallAtStart() == wall;
		}
		
		public Wall getWall()
		{
			return this.wall;
		}
		
		public Level getLevel()
		{
			return this.level;
		}
		
		public void resetJoinedWalls()
		{
			if (this.wallAtStart != null)
			{
				this.wall.setWallAtStart(this.wallAtStart);
				if (this.joinedAtEndOfWallAtStart)
				{
					this.wallAtStart.setWallAtEnd(this.wall);
				}
				else
				{
					this.wallAtStart.setWallAtStart(this.wall);
				}
			}
			if (this.wallAtEnd != null)
			{
				this.wall.setWallAtEnd(wallAtEnd);
				if (this.joinedAtStartOfWallAtEnd)
				{
					this.wallAtEnd.setWallAtStart(this.wall);
				}
				else
				{
					this.wallAtEnd.setWallAtEnd(this.wall);
				}
			}
		}
	}
	
	/**
	 * Stores the current properties values of a modified wall side.
	 */
	private static final class ModifiedWallSide
	{
		private final WallSide wallSide;
		private final Integer wallColor;
		private final HomeTexture wallTexture;
		private final Float wallShininess;
		private final Baseboard wallBaseboard;
		
		public ModifiedWallSide(WallSide wallSide)
		{
			this.wallSide = wallSide;
			Wall wall = wallSide.getWall();
			if (wallSide.getSide() == WallSide.LEFT_SIDE)
			{
				this.wallColor = wall.getLeftSideColor();
				this.wallTexture = wall.getLeftSideTexture();
				this.wallShininess = wall.getLeftSideShininess();
				this.wallBaseboard = wall.getLeftSideBaseboard();
			}
			else
			{
				this.wallColor = wall.getRightSideColor();
				this.wallTexture = wall.getRightSideTexture();
				this.wallShininess = wall.getRightSideShininess();
				this.wallBaseboard = wall.getRightSideBaseboard();
			}
		}
		
		public WallSide getWallSide()
		{
			return this.wallSide;
		}
		
		public void reset()
		{
			Wall wall = this.wallSide.getWall();
			if (this.wallSide.getSide() == WallSide.LEFT_SIDE)
			{
				wall.setLeftSideColor(this.wallColor);
				wall.setLeftSideTexture(this.wallTexture);
				wall.setLeftSideShininess(this.wallShininess);
				wall.setLeftSideBaseboard(this.wallBaseboard);
			}
			else
			{
				wall.setRightSideColor(this.wallColor);
				wall.setRightSideTexture(this.wallTexture);
				wall.setRightSideShininess(this.wallShininess);
				wall.setRightSideBaseboard(this.wallBaseboard);
			}
			Wall wallAtStart = wallSide.getWallAtStart();
			if (wallAtStart != null)
			{
				wall.setWallAtStart(wallAtStart);
				if (wallSide.isJoinedAtEndOfWallAtStart())
				{
					wallAtStart.setWallAtEnd(wall);
				}
				else
				{
					wallAtStart.setWallAtStart(wall);
				}
			}
			Wall wallAtEnd = wallSide.getWallAtEnd();
			if (wallAtEnd != null)
			{
				wall.setWallAtEnd(wallAtEnd);
				if (wallSide.isJoinedAtStartOfWallAtEnd())
				{
					wallAtEnd.setWallAtStart(wall);
				}
				else
				{
					wallAtEnd.setWallAtEnd(wall);
				}
			}
		}
	}
}
